package com.gitee.feizns.dynamic;

import com.gitee.feizns.dynamic.function.ThrowsThrowableRunnable;
import com.gitee.feizns.dynamic.function.ThrowsThrowableSupplier;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * <b>异常处理常用操作工具类</b>
 *
 * @see Exception
 * @see Throwable
 * @author feizns
 * @since 2019/6/21
 */
public abstract class ExUtils {

    /**
     * <b>忽略掉代码片段中的异常、而将null作为这段代码片段的返回值</b>
     *
     * <p>该方法一般应用于执行某个方法但不希望处理该方法的异常的情况.<br/>
     * <pre>{@code
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date date = ExUtils.ignore(() -> sdf.parse("2022-10-15"));
        System.out.println(date);   //Sat Oct 15 00:00:00 CST 2022

        Date nullD1 = ExUtils.ignore(() -> sdf.parse("ABC"));
        System.out.println(nullD1);   //null

        //将忽略所有异常
        ExUtils.ignore(() -> {throw new Exception("解析错误.");});
        ExUtils.ignore(() -> {throw new RuntimeException("解析错误.");});
        ExUtils.ignore(() -> {throw new ParseException("解析错误.", 0);}

        //仅忽略ParseException
        ExUtils.ignore(() -> {throw new ParseException("解析错误.", 0);}, ParseException.class);

        //抛出 java.lang.RuntimeException: java.lang.RuntimeException: 我不会被忽略.
        ExUtils.ignore(() -> {throw new RuntimeException("我不会被忽略.");}, ParseException.class);
     }</pre></p>
     *
     * <p><i>仅忽略部分异常的场景：</i>
     * <pre>{@code

     }</pre></p>
     * @see ThrowsThrowableSupplier 代码段（函数式编程接口）
     * @see #ignore(ThrowsThrowableSupplier, Predicate) 更多忽略异常的应用可以看
     * @param supplier 提供返回值的代码段
     * @param ignoreCourseClasses 仅忽略的异常类型|如果不填，则忽略所有异常
     * @return 代码段的返回值
     * @param <R> 代码段返回值类型
     * @throws RuntimeException 这个运行时异常会包装原代码段中抛出的且不被忽略的异常
     */
    @SafeVarargs
    public static <R> R ignore(ThrowsThrowableSupplier<R> supplier, Class<? extends Throwable>... ignoreCourseClasses) {
        try {
            return supplier.get();
        } catch (Throwable e) {
            //ignoreCourseClasses为空代表没有需要忽略的异常 ==> 会作为不忽略任何异常来处理
            if ( ArrayUtils.isNotEmpty(ignoreCourseClasses) ) {
                Throwable throwable = findTheCourse(e, ignoreCourseClasses);
                //仅在尝试查找需要忽略的异常却没有找到的情况下才抛出异常
                if ( Objects.isNull(throwable) )
                    throw new RuntimeException(e);
            }
            return null;
        }
    }

    /**
     * 忽略异常的的可执行代码
     * @param runnable 可运行
     * @param ignoreCourseClasses 忽视课程类
     */
    @SafeVarargs
    public static void ignore(ThrowsThrowableRunnable runnable, Class<? extends Throwable>... ignoreCourseClasses) {
        ignore(() -> { runnable.run(); return null; }, ignoreCourseClasses);
    }

    /**
     * <b>忽略掉代码片段中的异常、而将null作为这段代码片段的返回值</b>
     *
     * <p>该方法一般应用于执行某个方法但不希望处理该方法的异常的情况.<br/>
     * <pre>{@code
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    Date date = ExUtils.ignore(() -> sdf.parse("2022-10-15"), ex -> false); //不忽略异常
    System.out.println(date);   //Sat Oct 15 00:00:00 CST 2022

    Date nullD1 = ExUtils.ignore(() -> sdf.parse("ABC"), ex -> true); //忽略所有异常
    System.out.println(nullD1); //null

    Date nullD2 = ExUtils.ignore(() -> sdf.parse("ABC"), ex -> ex.getClass() == ParseException.class); //仅忽略ParseException类型的异常
    System.out.println(nullD2); //null

    Date throwD3 = ExUtils.ignore(() -> sdf.parse("ABC"), ex -> false); //抛出 java.lang.RuntimeException: java.text.ParseException:
    }</pre></p>
     *
     * @see ThrowsThrowableSupplier 代码段（函数式编程接口）
     * @param supplier 提供返回值的代码段
     * @param ignorePredicate 判断异常是否需要被忽略
     * @return 代码段的返回值
     * @param <R> 代码段返回值类型
     * @throws RuntimeException 这个运行时异常会包装原代码段中抛出的且不被忽略的异常
     */
    public static <R> R ignore(ThrowsThrowableSupplier<R> supplier, Predicate<Throwable> ignorePredicate) {
        try {
            return supplier.get();
        } catch (Throwable e) {
            if (ignorePredicate.test(e))
                return null;
            throw new RuntimeException(e);
        }
    }

    /**
     * <b>异常链中查找指定类型的异常</b>
     *
     * <p>
     *     如果{@code throwable}的异常链中是有匹配{@code courseClasses}类型中的任意一个则返回该原因对象、否则返回{@code null}
     * </p>
     *
     * @param throwable 所需要查找的异常
     * @param courseClasses 查找的原因类型
     * @return 符合查找原因类型的异常对象
     */
    @SafeVarargs
    public static Throwable findTheCourse(Throwable throwable, Class<? extends Throwable>... courseClasses) {
        Throwable course = throwable;
        while (course != null) {
            for (Class<? extends Throwable> courseClass : courseClasses) {
                if (courseClass.isAssignableFrom(course.getClass()))
                    return course;
            }
            course = course.getCause();
        }
        return null;
    }

    /**
     * <b>异常链中查找满足条件的原因</b>
     *
     * <p>
     *     查找{@code throwable}的异常链中匹配{@code predicate}判断条件的{@code course}对象，没有则返回{@code null}
     * </p>
     *
     * @param throwable 所需要查找的异常
     * @param predicate 查找判断逻辑
     * @return 符合判断条件的异常对象
     */
    public static Throwable findTheCourse(Throwable throwable, Predicate<Throwable> predicate) {
        Throwable course = throwable;
        while (course != null) {
            if (predicate.test(course))
                return course;
            course = course.getCause();
        }
        return null;
    }

    /**
     * 使用指定的运行时异常包装可能抛出的异常
     * @param supplier {@link ThrowsThrowableSupplier}
     * @param runtimeExceptionFunc {@link RuntimeException}
     * @return {@link T}
     */
    public static <T> T wrapWith(ThrowsThrowableSupplier<T> supplier, Function<Throwable, RuntimeException> runtimeExceptionFunc) {
        try {
            return supplier.get();
        } catch (Throwable t) {
            if ( runtimeExceptionFunc != null )
                throw runtimeExceptionFunc.apply(t);
        }
        return null;
    }

    /**
     * 使用指定的运行时异常包装可能抛出的异常
     * @param runnable {@link ThrowsThrowableRunnable}
     * @param runtimeExceptionFunc {@link RuntimeException}
     */
    public static void wrapWith(ThrowsThrowableRunnable runnable, Function<Throwable, RuntimeException> runtimeExceptionFunc) {
        try {
            runnable.run();
        } catch (Throwable t) {
            if ( runtimeExceptionFunc != null )
                throw runtimeExceptionFunc.apply(t);
        }
    }
    
    /**
     * 使用指定的运行时异常包装可能抛出的异常
     * @param supplier {@link ThrowsThrowableSupplier}
     * @return {@link T}
     */
    public static <T> T wrapWithRuntimeException(ThrowsThrowableSupplier<T> supplier) {
        return wrapWith(supplier, RuntimeException::new);
    }

    /**
     * 使用指定的运行时异常包装可能抛出的异常
     * @param runnable {@link ThrowsThrowableSupplier}
     */
    public static void wrapWithRuntimeException(ThrowsThrowableRunnable runnable) {
        wrapWith(runnable, RuntimeException::new);
    }

}
